#include <pthread.h>
#include "libvid.h"
#include "debug.h"
#include "constantes.h"
/* #include "/usr/include/libavformat/avformat.h" */

void			ml_usleep(value				us)
{
  usleep(Int_val(us));
}

void			*audio_th(void			*current)
{
  int16_t		*audio_buf;
  int			audio_buf_size;
  video			*cur = (video *) current;
  AVPacket		*pack;
  /* double pts; */
  /* int			n,len,len1; */

  audio_buf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); /* 99506 */
  audio_buf = memset(audio_buf, 0, AVCODEC_MAX_AUDIO_FRAME_SIZE);

  for (; ; )
  { /* printf ("audio %p\n",(void *) cur); */
    if (!q_empty(cur->audio_queue))
    {
      audio_buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
      pack = pull(&cur->audio_queue);

      /* pts = is->audio_clock; */

      /* Decode it! */
      avcodec_decode_audio3(cur->pAudioCodec,
			    audio_buf,
			    &audio_buf_size,
			    pack);

      /* n = 2 * cur->pAudioCodec->channels; */
      /* cur->audio_clock += (double)audio_buf_size / */
      /* 	(double)(n * cur->pAudioCodec->sample_rate); */

      /* len = cur->audio_buf_size; */
      /* len1 = cur->audio_buf_size - cur->audio_buf_index; */

      /* if (len1 > len) */
      /* 	len1 = len; */
      /* cur->audio_buf_index += len1; */


      /* if(pack->pts != AV_NOPTS_VALUE) */
      /* { */
      /* 	cur->audio_clock = av_q2d(cur->pAudioCodec->time_base)*pack->pts; */
      /* } */

      /* cur->video_current_pts = cur->audio_clock; */
      /* cur->video_current_pts_time = av_gettime(); */

      av_free_packet(pack);

      if (sendserver(cur->pass,audio_buf,audio_buf_size) == -1)
	printf ("send server error\n");
    }
    usleep(1);
    /* nanosleep(cur->tim, NULL); */
  }
}

void			*video_th(void				*current)
{
  int			finished = 0;
  video			*cur = (video *) current;
  AVPacket		*pack;

  for (; ; )
  { /* printf ("video %p\n",(void *) cur); */
    if (!q_empty(cur->video_queue))
    {
      pack = pull(&cur->video_queue);

      /* Decode it! */
      avcodec_decode_video2(cur->pCtx,
			    cur->pRaw,
			    &finished,
			    pack);

      av_free_packet(pack);

      /* gcc gets trippy because of the double const usage in argument 2 - hard cast
       * Scale & convert decoded frame to correct size & colorspace
       */
      sws_scale(cur->Sctx, (const uint8_t* const *) cur->pRaw->data,
		cur->pRaw->linesize, 0, cur->pCtx->height,
		cur->pDat->data, cur->pDat->linesize);


      cur->new_frame = 1;
    }

    usleep(1);
    /* nanosleep(cur->tim, NULL); */
  }
}

video		        *video_init(value			fichier,
				    value			ba)
{
  int			i = 0;
  int			w = 0;
  int			h = 0;
  int			format = PIX_FMT_RGB24; /* grepme useless var */
  char			*file = String_val(fichier);
  video			*ret = malloc(sizeof (struct _video));
  int			rate = 0;
  int			channels = 0;
  pa_sample_format_t	sam_format = 0;
  int			*p;


  memset(ret, 0, sizeof (struct _video));
  ret->format = format;

  /* Init ffmpeg */
  av_register_all();

  /* Open file, check usability */
  if (av_open_input_file(&ret->pFormatCtx, file, NULL, 0, NULL)
      || av_find_stream_info(ret->pFormatCtx) < 0)
    video_quit(ret);

  /* Find the first video stream */
  ret->videoStream = -1;
  ret->fluxAudio = -1;

  for (i = 0; abs(i) < ret->pFormatCtx->nb_streams; i++)
    if(ret->pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
    {
      ret->videoStream = i;
      break;
    }

  for(i = 0; abs(i) < ret->pFormatCtx->nb_streams; i++)
    if (ret->pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
    {
      ret->fluxAudio = i;
      break;
    }

  /* if (ret->videoStream == -1) */
    /* video_quit(ret); */

  if (ret->fluxAudio == -1)
    video_quit(ret);

  /* Get context for codec, pin down target width / height, find codec */

  if (ret->videoStream != -1)
  {
    ret->pCtx = ret->pFormatCtx->streams[ret->videoStream]->codec; /* grepme dodge mp3 failure */
    ret->width = w ? w : ret->pCtx->width;
    ret->height = h ? h : ret->pCtx->height;


    /* framerate sender */
    p = Data_bigarray_val(ba);
    p[IBA_FRAME_RATE_NUM] = ret->pCtx->time_base.num;
    p[IBA_FRAME_RATE_DEN] = ret->pCtx->time_base.den;

  }

  ret->pAudioCodec = ret->pFormatCtx->streams[ret->fluxAudio]->codec;

  if (ret->videoStream != -1)
  {
    ret->pCodec = avcodec_find_decoder(ret->pCtx->codec_id); /* grepme dodge mp3 failure */

    if (!ret->pCtx) /* video Codec non supporté */
      video_quit(ret);
  }

  ret->pAudio = avcodec_find_decoder(ret->pAudioCodec->codec_id);

  if (!ret->pAudio) /* audio Codec non supporté */
      video_quit(ret);

  if ((avcodec_open(ret->pAudioCodec, ret->pAudio) < 0)
      || (ret->videoStream != -1)
      && (!ret->pCodec
	  || avcodec_open(ret->pCtx, ret->pCodec) < 0))
    video_quit(ret);

  /* Frame rate fix for some codecs */
  if ((ret->videoStream != -1) &&
      (ret->pCtx->time_base.num > 1000 && ret->pCtx->time_base.den == 1))
    ret->pCtx->time_base.den = 1000;

  /* Get framebuffers */

  ret->pRaw = avcodec_alloc_frame();

  if (ret->videoStream != -1)
    ret->pDat = avcodec_alloc_frame();

  if (!ret->pRaw
      || ((ret->videoStream != -1)
	  && !ret->pDat))
    video_quit(ret);

  /* Create data buffer */
  if (ret->videoStream != -1)
  {
    ret->buffer =
      malloc(avpicture_get_size(ret->format,ret->pCtx->width, ret->pCtx->height));

  /* Init buffers */
    avpicture_fill(
      (AVPicture *) ret->pDat, ret->buffer, ret->format,
      ret->pCtx->width, ret->pCtx->height);

  /* Init scale && convert */
    ret->Sctx =
      sws_getContext(ret->pCtx->width, ret->pCtx->height, ret->pCtx->pix_fmt,
		     ret->width, ret->height, ret->format,
		     SWS_BICUBIC, NULL, NULL, NULL);

    if (!ret->Sctx)
      video_quit(ret);
  }
  /* Give some info on stderr about the file & stream */
  /* dump_format(ret->pFormatCtx, 0, file, 0); */

  rate = ret->pAudioCodec->sample_rate;
  channels = ret->pAudioCodec->channels;

  switch (ret->pAudioCodec->sample_fmt)
  {
  case SAMPLE_FMT_U8:
    sam_format = PA_SAMPLE_U8;
    break;

  case SAMPLE_FMT_S16:
    sam_format = PA_SAMPLE_S16LE;
    break;

  case SAMPLE_FMT_S32:
    sam_format = PA_SAMPLE_S32LE;
    break;

  default:
    printf("what the CASE !\n");
  }

  ret->pass = initserver(sam_format, rate, channels);


  /* THREADS */

  /* printf("orig %p\n",(void *) ret); */

  ret->tim = malloc(sizeof (struct timespec));

  ret->tim->tv_sec = 0;
  ret->tim->tv_nsec = 0;

  ret->ath = malloc(sizeof (pthread_t));
  ret->vth = malloc(sizeof (pthread_t));

  pthread_create(ret->ath, NULL, audio_th, (void *) ret);

  if (ret->videoStream != -1)
    pthread_create(ret->vth, NULL, video_th, (void *) ret);


  /* /\* SEEK_INIT *\/ */
  /* ret->audio_clock = 0.0; */
  /* ret->av_sync_type = DEFAULT_AV_SYNC_TYPE; */
  /* ret->audio_buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE/\* 99506 *\/; */
  /* ret->audio_buf_index = 0; */
  /* ret->video_current_pts = ret->audio_clock; */
  /* ret->video_current_pts_time = av_gettime(); */

  return (ret);
}

value			video_next(video			*cur)
{
  AVPacket		*pack;
  int			stream_index;
  int64_t		seek_target;

  int			finished = 0;
  int			i;
  int			audio_buf_size ;


  caml_release_runtime_system();
  cur->new_frame = 0;
  usleep(1);

  /* nanosleep(cur->tim, NULL); */
  /* printf ("next %p\n",(void *) cur); */

  if ((q_num(cur->video_queue) + q_num(cur->audio_queue)) < 30)
  {

    pack = malloc(sizeof (AVPacket));
    av_init_packet(pack);

    /*  seek */
    if (cur->seek_req)
    {
      stream_index = -1;
      seek_target = cur->seek_pos;

      if  (cur->videoStream >= 0)
    	stream_index = cur->videoStream;

      else
    	if (cur->fluxAudio >= 0)
    	  stream_index = cur->fluxAudio;

      if (stream_index>=0)
    	seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q,
    				  cur->pFormatCtx->streams[stream_index]->time_base);

      if (av_seek_frame(cur->pFormatCtx, stream_index,
      			seek_target, cur->seek_flags) < 0);
      /* if (AVInputFormat.read_seek2(cur->pFormatCtx, stream_index, seek_target, cur->seek_flags) < 0) */
      /* 	fprintf(stderr, "%s: error while seeking\n", cur->pFormatCtx->filename); */
    }
    pack = malloc(sizeof (AVPacket));


    /* av_init_packet(pack); */

    /* Can we read a frame? */
    if (av_read_frame(cur->pFormatCtx, pack) < 0)
      return (Val_int(-1));

    /* Is it what we're trying to parse ? */
    if (pack)
    {
      if (pack->stream_index == cur->videoStream)
      {
	cur->video_queue = push (pack, cur->video_queue);
	return (Val_int(1));
      }
      else
	if (pack->stream_index == cur->fluxAudio)
	  cur->audio_queue = push (pack, cur->audio_queue);
	else
	  av_free_packet(pack);
    }
  }

  caml_acquire_runtime_system();

  return (Val_int(cur->new_frame));
}


/*
 * Close & Free cur video context
 * This function is also called on failed init
 * so check existence before de-init
 */
value			video_quit(video		*cur)
{
  if(cur->Sctx)
    sws_freeContext(cur->Sctx);

  if(cur->pRaw)
    av_free(cur->pRaw);

  if(cur->pDat)
    av_free(cur->pDat);

  if(cur->pCtx)
    avcodec_close(cur->pCtx);

  if(cur->pFormatCtx)
    av_close_input_file(cur->pFormatCtx);

  free(cur->buffer);
  free(cur);

  return (0);
}

/* int width(video *cur) */
value			width(video			*current)
{
  return Val_int(current->width);
}

/* int height(video *cur) */
value			height(video			*current)
{
  return Val_int(current->height);
}


value			pixel(video			*cur,
			      value			i,
			      value			j)
{
  int			colone = Int_val(i);
  int			ligne = Int_val(j);

  uint8_t		*ln = cur->pDat->data[0] + ligne * cur->pDat->linesize[0];

  int			r = (int) ln[3*colone];
  int		       	v = (int) ln[3*colone+1];
  int			b = (int) ln[3*colone+2];

  int			res = r * 16 * 16 * 16 * 16 + v * 16 * 16 + b;

  /* printf("%i %i\n",colone,ligne); */
  return Val_int(res);
}


/* double			get_audio_clock(video			*is) */
/* { */
/*   double		pts; */
/*   int			hw_buf_size, bytes_per_sec, n; */

/*   pts = is->audio_clock; */
/*   hw_buf_size = is->audio_buf_size - is->audio_buf_index; */
/*   bytes_per_sec = 0; */
/*   n = is->pAudioCodec->channels * 2; */
/*   if (is->pAudioCodec) */
/*     bytes_per_sec = is->pAudioCodec->sample_rate * n; */

/*   if (bytes_per_sec) */
/*     pts -= (double)hw_buf_size / bytes_per_sec; */

/*   return pts; */
/* } */


/* double			get_video_clock(video *is) */
/* { */
/*   double		delta; */

/*   delta = (av_gettime() - is->video_current_pts_time) / 1000000.0; */
/*   return is->video_current_pts + delta; */

/* } */

/* double get_external_clock(video/\* VideoState *\/ *is) */
/* { */
/*   return av_gettime() / 1000000.0; */
/* } */


/* double get_master_clock(video *is) */
/* { */
/*   if(is->av_sync_type == AV_SYNC_VIDEO_MASTER) */
/*     { */
/*       return get_video_clock(is); */
/*     } */
/*   else if(is->av_sync_type == AV_SYNC_AUDIO_MASTER) */
/*     { */
/*       return get_audio_clock(is); */
/*     } */
/*   else */
/*     { */
/*       return get_external_clock(is); */
/*     } */
/* } */

void			stream_seek(video		*is,
				    int64_t		pos,
				    int			rel)
{
  if (!is->seek_req)
  {
    is->seek_pos = pos;
    is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0;
    is->seek_req = 1;

    debug_pause();
    printf("w8ing\n");
  }
}
